Statements
Code Block
Statements could be grouped in paired curly brackets. Local variables defined in a code block are not visible outside the block.
{
int32 i;
}
// i no longer defined
if-, for- and some other statements are always followed by a code block as part of the statement. The body of a function definition can also be regarded as a code block.
Variable Declaration Statement
PREDA is a statically-typed language and all variables must be assigned a static type at definition.
MyType myVariable = initializer;
The initializer is optional but its type must match the type used if it's present.
'auto' Keyword
Another way to specify the type is to use the 'auto' keyword. In this case, an initializer must be provided.
auto x = 1u16; // x is defined as uint16
auto y = "123"; // y is defined as string
auto z; // compile error
The scope of the defined variable is the innermost block that contains it. A variable cannot shadow another one with the same name defined in an outer scope. Instead, it will generate a compile error.
{
int32 i;
}
int32 i; // the i defined above is no longer available here. Hence a new definition of i is possible.
int32 j;
{
int32 j; // re-defining j here will not shadow the definition in the outer scope. Instead, it gets a compile error
}
'const' Keyword
A variable can also be declared as a constant using the 'const' keyword. The data of a constant variable cannot be changed once it's initialized from the initializer in its declaration statement.
const int32 i = 3; // declaring i as constant
i = 4; // Compile error: a constant value cannot be modified after initialization
Constant variables of reference types behave a bit differently with the assignment operator '=', because it shares the underlying data instead of making a copy. Therefore, for reference types, assigning a constant variable to a non-constant variable would generate a compile error. Otherwise the shared data would be modifiable through the non-constant variable.
struct S{
bool a;
}
const S s0;
S s1;
s1 = s0; // Compile error: Cannot assign a constant reference type to non-constant.
if Statement
if statement has the following syntax
if (condition) {
// statements when condition is satisfied
}
else {
// statements when condition is not satisfied
}
if and else must always be followed by a block, even if there's only one statement in it. The only exception is when else is immediately followed by an if, so they can be chained together like:
if (...) {
}
else if (...) {
}
else if (...) {
}
else {
}
for, do-while and while Statements
for, do-while and while statements has the following syntax
for (init-statement; condition; iteration-expression){
// loop body
}
do{
// loop body, executed at least once
} while (condition);
while (condition){
// loop body
}
for, do-while and while must always be followed by a block, even if there's only one statement in it.
continue and break Statements
continue statement is used to skip the rest of loop body in for, do-while or while statements for the current loop. break statement is used to terminate the corresponding loop statement.
return Statement
return statement is used to end the execution in current function and return to the caller. If the current function has a return type, it must be followed by an expression of the same type.
relay Statement
A relay statement is similar to a function call, except that the call is asynchronous. The call data is packaged in a so-called "relay transaction" and relayed to the target for execution. The relay statement itself returns immediately.
relay@TargetExpression functionName(params);
relay@shards functionName(params);
relay@global functionName(params);
There are 3 types of relay targets, as shown in the above example. The first type is the general form, where TargetExpression is an expression that evaluates to a type that matches the function's scope.
The second type is a broadcast relay, which uses the 'shards' keyword. It relays to all the non-global shards, like a broadcast. In this case, the called function must be defined in the shard scope.
The last type is a global relay, which uses the 'global' keyword. It relays to the global shard and must be called from a shard- or address- function. The function must be defined in the global scope.
In all types, the function being called must be from the same contract.
relay Statement with Lambda Function
Alternatively, relay statement define a lambda function inline and relay to it.
relay@TargetExpression|'shards'|'global' (['const'] parameterType parameterName = argumentExpression, ...) ['const']{
// function body
}
The format is quite similar to defining a function except that:
- A function name is not needed. The compiler automatically generates a name for it.
- The scope of the anonymous function is TargetExpression, shard or global, based on the relay type.
- For each parameter, an argument must be provided as well.
- It is possible to use the 'auto' keyword as parameter type. In this case, the type is taken from the corresponding argument expression.
Be aware that the relay function body is executed on the per-address context of TargetAddress, the per-shard context of the target shards, or the global context. It is not to be mixed with the current context on which the relay statement is invoked.
To simplify the code, there's another way to specify a parameter in the relay lambda:
relay@someAddress (..., ^identifier, ...){
}
This is exactly the same as
relay@someAddress (..., auto identifier = identifier, ...){
}
deploy Statement
The deploy statement is used to programmatically create a new contract on chain from within a contract.
deploy contractName(parameters);
For more details check Deploy Unnamed Contract.